home *** CD-ROM | disk | FTP | other *** search
/ SoundMaker 2003 (Professional Edition) / SoundMaker 2003 - Professional Edition.iso / midi tool / midioxse.exe / DATA.1 / ARPEGI.REX next >
OS/2 REXX Batch file  |  1999-03-27  |  15KB  |  597 lines

  1. /* REXX: MIDI Arpeggiator                */
  2. /* Copyright (c) 1998 by Jamie O'Connell */
  3. /* Designed to be used along with diverting of input */
  4.  
  5. signal on NOVALUE
  6.  
  7. call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
  8. call SysLoadFuncs 
  9.  
  10. /* ask for the channel byte split off */
  11. qName = MOXSetQueueDataFormat( 'C' )
  12. oldq  = RxQueue( 'Set', qName )
  13.  
  14.  
  15. /* MIDI-OX creates a semaphore with same name as queue */
  16. sem = SysOpenEventSem( qName )
  17.  
  18. running = (sem <> 0)    
  19.  
  20. /* Set up the arpeggiator object */
  21. /* TEMP: Use defaults */
  22. now = MoxGetSystemTime()
  23.  
  24. parse arg t r s o c
  25.  
  26. tempo = 120   
  27. if t <> '' then
  28.    tempo = t
  29.  
  30. resolution = 0.25                /* 16th note */
  31. if r <> '' then
  32.    resolution = r
  33.  
  34. sustain = 2                   /* stacatto  */
  35. if s <> '' then
  36.    sustain = s                   /* stacatto  */
  37.  
  38. octaves = 2                   /* 2 octaves */
  39. if o <> '' then
  40.    octaves = o
  41.  
  42. cycle = 3                   /* Up arpeggiator */
  43. if c <> '' then
  44.   cycle = c
  45.         
  46. Arp = .arpeggiator~New( now, tempo, resolution, sustain, octaves, cycle )
  47. say "Tempo (BPM):" tempo
  48. say "Resolution:" Arp~ResText() "(" resolution ")"
  49. say "Sustain:" Arp~SusText() "(" sustain ")"
  50. say "Octaves:" octaves
  51. say "Cycle:" Arp~CycleText() "(" cycle ")"
  52. say "Arpegi Initialized..."
  53. last = 0
  54.  
  55. /* Main loop */
  56. do while running
  57.    someInput = (Queued() <> 0) 
  58.    someChord = (Arp~IsEmpty() = .FALSE) 
  59.     
  60.    do while someInput | someChord
  61.       /* get current timestamp */
  62.       now = MoxGetSystemTime()        
  63.       if someInput 
  64.       then do 
  65.          pull timestamp status chan data1 data2         
  66.  
  67.          /* Sent by MIDI-OX to signify end of program */
  68.          if timestamp = 'END_DATA' 
  69.          then do
  70.             running = 0
  71.             leave
  72.          end
  73.  
  74.          if status = 144 /* Note on */
  75.          then do
  76.             if data2 > 0 
  77.             then do
  78.                Arp~Add( now, chan, data1, data2 )
  79.             end
  80.             else 
  81.                status = 128 /* Note off (0 velocity) */
  82.          end
  83.  
  84.          if status = 128 then 
  85.             Arp~Remove( chan, data1 )
  86.          else if status <> 144 then /* assume we're diverted */
  87.             ret = MOXOutputMidiC( status, chan, data1, data2 )            
  88.       end
  89.  
  90.       someChord = (Arp~IsEmpty() = .FALSE) 
  91.       if someChord 
  92.       then do /* see if the queue head has expired yet */
  93.          Arp~MaybeTrigger( now )
  94.       end
  95.    
  96.       someInput = (Queued() <> 0)
  97.    end
  98.  
  99.    if running 
  100.    then do
  101.       /* queue is empty: you could do other processing here */       
  102.       ret = SysWaitEventSem( sem )
  103.       if ret <> 0 
  104.       then do
  105.          running = .false
  106.          say "Error SysWaitEventSem Code:" ret          
  107.       end
  108.    end
  109. end
  110.  
  111. /* close up shop */
  112. call SysDropFuncs 
  113.  
  114. say "Arpegi Closing... "
  115. exit 0
  116.  
  117. ::requires "arrayx.cls"
  118.  
  119. /* ========================================================== */
  120.  
  121. ::class Arpeggiator subclass Object
  122. ::method Init
  123.    expose now tempo resolution sustain octaves cycle noteAry,
  124.           noteOnQ noteOffQ playQ period duration seqCount 
  125.    use arg now, tempo, resolution, sustain, octaves, cycle 
  126.  
  127.    noteAry  = .arrayx~new( 32 )
  128.    noteOnQ  = .queue~new
  129.    noteOffQ = .queue~new
  130.    playQ    = .queue~new
  131.    period   = self~CalcPeriod( tempo, resolution ) 
  132.    duration = self~CalcDuration( period, sustain )
  133.    seqCount = 0
  134.    return
  135.  
  136. /* ---------------------------------------------------------- */
  137. ::method now        attribute
  138. ::method tempo      attribute
  139. ::method resolution attribute 
  140. ::method sustain    attribute 
  141. ::method octaves    attribute
  142. ::method cycle      attribute
  143. ::method period     attribute
  144. ::method duration   attribute
  145. ::method seqCount   attribute
  146.  
  147. /* ---------------------------------------------------------- */
  148. ::method ResText 
  149.     expose resolution
  150.  
  151.    if resolution <= 0.0625 then
  152.       return "1/64th note"
  153.  
  154.    if resolution <= 0.125 then
  155.       return "1/32nd note"
  156.  
  157.    if resolution <= 0.25 then
  158.       return "1/16th note"
  159.  
  160.    if resolution <= 0.5 then
  161.       return "1/8th note"
  162.  
  163.    if resolution <= 1 then
  164.       return "1/4 note"
  165.  
  166.    if resolution <= 2 then
  167.       return "1/2 note"
  168.  
  169.    return "Whole note"
  170.  
  171. /* ---------------------------------------------------------- */
  172. ::method SusText 
  173.    expose sustain
  174.  
  175.    select
  176.       when sustain = 1 then 
  177.          return "Very Short"
  178.       when sustain = 2 then 
  179.          return "Staccato"
  180.       when sustain = 3 then 
  181.      return "Legatto"
  182.       otherwise
  183.      return "Unknown"
  184.    end
  185.  
  186.    return "Unknown"
  187.  
  188. /* ---------------------------------------------------------- */
  189. ::method CycleText 
  190.    expose cycle
  191.  
  192.    select
  193.       when cycle = 1 then 
  194.          return "Up"
  195.       when cycle = 2 then 
  196.          return "Down"
  197.       when cycle = 3 then 
  198.          return "Up-down"
  199.       when sustain = 4 then 
  200.          return "Down-up"
  201.       otherwise
  202.      return "Unknown"
  203.    end
  204.  
  205.    return "Unknown"
  206.  
  207. /* ---------------------------------------------------------- */
  208.  
  209. ::method UpdateParms
  210.     expose tempo resolution sustain octaves cycle period duration
  211.     use arg tempo, resolution, sustain, octaves, cycle
  212.  
  213.    period   = self~CalcPeriod( tempo, resolution ) 
  214.    duration = self~CalcDuration( period, sustain )
  215.     return
  216.  
  217. /* ---------------------------------------------------------- */
  218. ::method IsEmpty
  219.    expose noteAry
  220.    return noteAry~IsEmpty()
  221.  
  222. /* ---------------------------------------------------------- */
  223. ::method CalcPeriod
  224.    use arg bpm, nBeats
  225.   
  226.    /* 60 sec/min * 1000 msec/sec / bbm * beats (fractional) */
  227.    dur = (60000 / bpm) * nBeats  
  228.    return dur
  229.  
  230. /* ---------------------------------------------------------- */
  231. ::method CalcDuration
  232.    use arg len, susCode
  233.  
  234.    /* susCode is 1=short, 2=stacatto, 3=legato  (1/3, 2/3, 3/3) */
  235.    /* We shorten the period a few msecs beyond that */
  236.    dur = (len * susCode / 3) - 2
  237.    return dur
  238.  
  239. /* ---------------------------------------------------------- */
  240.  
  241. ::method Add
  242.    expose now noteAry 
  243.    use arg now, chan, note, veloc 
  244.    
  245.    evt = .noteEvent~New( chan, note, veloc, self~duration )
  246.  
  247.     count = noteAry~HiBound()   
  248.     inserted = .FALSE
  249.    do ii = 1 to count
  250.       if note < noteAry[ ii ]~note
  251.          then do
  252.             noteAry~Insert( evt, ii )
  253.          inserted = .TRUE
  254.          leave
  255.         end
  256.     end
  257.     
  258.    if inserted <> .TRUE then
  259.         noteAry~Add( evt )
  260.  
  261.    self~Retrigger( now )
  262.    return
  263.  
  264. /* ---------------------------------------------------------- */
  265.  
  266. ::method Remove
  267.    expose noteAry noteOnQ noteOffQ playQ
  268.    use arg chan, note
  269.  
  270.    /* do removal in reverse order so we don't miss any events when the */
  271.    /* indexes renumber */
  272.     count = noteAry~HiBound()   
  273.  
  274.    do ii = count to 1 by -1
  275.       if noteAry[ ii ]~note = note & noteAry[ ii ]~chan = chan 
  276.       then do
  277.             id = noteAry[ ii ]~id
  278.             
  279.          do jj = playQ~Items to 1 by -1 
  280.             if playQ[ jj ]~index = id
  281.             then do
  282.                self~noteOff( chan, playQ[ jj ]~CalcPitch( note ) )
  283.                playQ~Remove( jj )
  284.             end
  285.          end
  286.  
  287.          do jj = noteOnQ~Items to 1 by -1
  288.             if noteOnQ[ jj ]~index = id then
  289.                noteOnQ~Remove( jj )
  290.          end
  291.  
  292.          do jj = noteOffQ~Items to 1 by -1
  293.             if noteOffQ[ jj ]~index = id then
  294.                noteOffQ~Remove( jj )
  295.          end
  296.          noteAry~Remove( ii )
  297.       end
  298.    end
  299.  
  300.    return
  301.  
  302. /* ---------------------------------------------------------- */
  303.  
  304. ::method playOff
  305.    expose noteAry playQ
  306.  
  307.    do while playQ~Items > 0
  308.       evt = playQ~Pull()
  309.       if evt <> .NIL
  310.       then do
  311.             noteEvt = self~FindEvent( evt~Index )
  312.             if noteEvt <> .NIL 
  313.             then do 
  314.                self~noteOff( noteEvt~Chan, evt~CalcPitch( noteEvt~Note ) )
  315.             end
  316.       end
  317.    end
  318.  
  319.    return
  320.  
  321. /* ---------------------------------------------------------- */
  322.  
  323. ::method noteOff
  324.    use arg chan, note
  325.  
  326.    ret = MOXOutputMidiC( 128, chan, note, 0 )
  327.    return
  328.  
  329. /* ---------------------------------------------------------- */
  330. ::method noteOn
  331.    use arg chan, note, veloc
  332.  
  333.    ret = MOXOutputMidiC( 144, chan, note, veloc )
  334.    return
  335.  
  336. /* ---------------------------------------------------------- */
  337. ::method Retrigger
  338.    expose cycle noteAry noteOnQ noteOffQ
  339.    use arg now
  340.  
  341.    self~playOff()
  342.  
  343.    /* resequence note ID's */
  344.     count = noteAry~HiBound()
  345.    do ii = 1 to count 
  346.         noteAry[ ii ]~id = ii
  347.    end
  348.     
  349.    /* clear out queues */
  350.    do ii = noteOnQ~Items to 1 by -1 
  351.       noteOnQ~Remove( ii )
  352.    end
  353.  
  354.    do ii = noteOffQ~Items to 1 by -1 
  355.       noteOffQ~Remove( ii )
  356.    end
  357.  
  358.    trigger = now + 20
  359.  
  360.    /* combo box choices (1 - 4) */
  361.    select
  362.       when cycle = 1 then
  363.          self~FillUp( trigger )
  364.       when cycle = 2 then
  365.          self~FillDown( trigger )
  366.       when cycle = 3 then
  367.          self~FillUpDown( trigger )
  368.       when cycle = 4 then
  369.          self~FillDownUp( trigger )
  370.       otherwise
  371.          say "Invalid Cycle:" cycle
  372.    end  
  373.  
  374.    return
  375.  
  376. /* ---------------------------------------------------------- */
  377.  
  378. ::method FindEvent
  379.     expose noteAry
  380.     use arg index
  381.  
  382.    do ii = (index + 1) to 1 by -1               
  383.       if noteAry[ ii ] = .NIL then  /* possible if index > hiBound */
  384.          iterate
  385.  
  386.       if noteAry[ ii ]~id = index then
  387.             return noteAry[ ii ]
  388.     end
  389.     
  390.     return .NIL
  391.  
  392. /* ---------------------------------------------------------- */
  393. ::method createEvents
  394.    expose noteAry noteOnQ noteOffQ
  395.     use arg idx, octv, trigtime
  396.  
  397.    evt = .qEvent~New( idx, octv, trigtime )
  398.    noteOnQ~Queue( evt )
  399.  
  400.    trigOff = noteAry[ idx ]~duration + trigtime
  401.    evt = .qEvent~New( idx, octv, trigOff )
  402.    noteOffQ~Queue( evt )
  403.     
  404.    trigtime = trigtime + self~Period
  405.    return trigtime
  406.  
  407. /* ---------------------------------------------------------- */
  408. ::method FillUp
  409.    expose octaves seqCount noteAry
  410.    use arg now
  411.  
  412.    trigger = now
  413.     count = noteAry~HiBound()
  414.    seqCount = 0
  415.  
  416.    do ii = 1 to octaves
  417.       do jj = 1 to count
  418.            trigger = self~createEvents( jj, ii, trigger )  
  419.          seqCount = seqCount + 1
  420.       end
  421.    end
  422.  
  423.    return
  424.  
  425. /* ---------------------------------------------------------- */
  426. ::method FillDown
  427.    expose octaves noteAry seqCount
  428.    use arg now
  429.  
  430.    trigger = now
  431.     count = noteAry~HiBound()
  432.    seqCount = 0
  433.  
  434.    do ii = octaves to 1 by -1 
  435.       do jj = count to 1 by -1 
  436.            trigger = self~createEvents( jj, ii, trigger )  
  437.          seqCount = seqCount + 1
  438.       end
  439.    end
  440.  
  441.    return
  442.  
  443. /* ---------------------------------------------------------- */
  444. ::method FillUpDown
  445.    expose octaves noteAry seqCount
  446.    use arg now
  447.  
  448.    trigger = now
  449.     count = noteAry~HiBound()
  450.    seqCount = 0
  451.  
  452.    do ii = 1 to octaves
  453.       do jj = 1 to count 
  454.            trigger = self~createEvents( jj, ii, trigger )  
  455.          seqCount = seqCount + 1
  456.       end
  457.    end
  458.  
  459.    do ii = octaves to 1 by -1
  460.       do jj = count to 1 by -1
  461.          if ii = octaves & jj = count then
  462.             iterate  /* don't play first note coming down */
  463.  
  464.          if ii = 1 & jj = 1 then
  465.             iterate  /* don't play last note coming down */
  466.  
  467.            trigger = self~createEvents( jj, ii, trigger )
  468.          seqCount = seqCount + 1  
  469.       end
  470.    end
  471.  
  472.    return
  473.  
  474. /* ---------------------------------------------------------- */
  475. ::method FillDownUp
  476.    expose octaves seqCount noteAry
  477.    use arg now
  478.  
  479.    trigger = now
  480.     count = noteAry~HiBound()
  481.    seqCount = 0
  482.  
  483.    do ii = octaves to 1 by -1 
  484.       do jj = count to 1 by -1 
  485.            trigger = self~createEvents( jj, ii, trigger )  
  486.          seqCount = seqCount + 1
  487.       end
  488.    end
  489.  
  490.    do ii = 1 to octaves
  491.       do jj = 1 to count 
  492.          if ii = octaves & jj = count then
  493.             iterate  /* don't play last note going up */
  494.  
  495.          if ii = 1 & jj = 1 then
  496.             iterate  /* don't play first note going up */
  497.  
  498.            trigger = self~createEvents( jj, ii, trigger )  
  499.          seqCount = seqCount + 1
  500.       end
  501.    end
  502.  
  503.    return
  504.  
  505. /* ---------------------------------------------------------- */
  506. ::method MaybeTrigger
  507.    expose noteOnQ noteOffQ playQ
  508.    use arg now
  509.  
  510.    nextTime = self~period * self~seqCount  
  511.  
  512.    /* has a note off trigger expired? */
  513.    evt = noteOffQ~Peek()
  514.    if evt <> .NIL
  515.    then do
  516.       if evt~trigger <= now
  517.       then do
  518.          trgEvt = self~FindEvent( evt~Index )
  519.             if trgEvt <> .NIL
  520.             then do
  521.              pitch  = evt~CalcPitch( trgEvt~Note )
  522.              self~noteOff( trgEvt~chan, pitch )
  523.  
  524.              do ii = playQ~Items to 1 by -1 
  525.                 if playQ[ ii ]~index = evt~Index then 
  526.                    playQ~Remove( ii )
  527.              end
  528.       
  529.              evt = noteOffQ~Pull()
  530.              evt~trigger = evt~trigger + nextTime
  531.              noteOffQ~Queue( evt )
  532.             end
  533.       end
  534.    end
  535.  
  536.    /* has a note on trigger expired? */
  537.    evt = noteOnQ~Peek()
  538.    if evt <> .NIL 
  539.    then do
  540.       if evt~trigger <= now
  541.       then do
  542.          trgEvt = self~FindEvent( evt~Index )
  543.             if trgEvt <> .NIL
  544.             then do
  545.              pitch  = evt~CalcPitch( trgEvt~Note )
  546.              self~noteOn( trgEvt~chan, pitch, trgEvt~velocity )
  547.              evt = noteOnQ~Pull()
  548.              newEvt  = .qEvent~New( evt~Index, evt~octMult, evt~trigger )
  549.              playQ~Queue( newEvt )
  550.              evt~trigger = evt~trigger + nextTime
  551.              noteOnQ~Queue( evt )
  552.             end
  553.       end
  554.    end
  555.  
  556.    return
  557.  
  558. /* ========================================================== */
  559.  
  560. ::class noteEvent subclass Object
  561.  
  562. ::method Init
  563.   expose chan note velocity duration id
  564.   use arg chan, note, velocity, duration
  565.   id = -1  /* must be filled later */
  566.   return
  567.  
  568. ::method id       attribute
  569. ::method chan     attribute
  570. ::method note     attribute
  571. ::method velocity attribute
  572. ::method duration attribute 
  573.  
  574. /* ========================================================== */
  575.  
  576. ::class qEvent subclass Object
  577.  
  578. ::method Init
  579.   expose index octMult trigger
  580.   use arg index, octMult, trigger
  581.   return
  582.  
  583. /* ---------------------------------------------------------- */
  584.  
  585. ::method index   attribute
  586. ::method octMult attribute 
  587. ::method trigger attribute
  588.  
  589. /* ---------------------------------------------------------- */
  590. ::method CalcPitch
  591.   expose octMult
  592.   use arg note
  593.   
  594.   pitch = (octMult - 1) * 12 + note
  595.   return pitch
  596.  
  597.